[TOC]
KVC
Key-Value Coding 基本原则
访问对象属性
1 | @interface BankAccount: NSobject |
currentBalance
/owner
/transactions
都是 BankAccount
的属性。owner
属性是一个对象,和BankAccount
构成一对一的关系,owner对象中的属性改变后并不会影响到owner本身。
为了保持封装,对象通常为其接口上的属性提供访问器方法(accessor methods)。在使用访问器方法时必须在编译之前将属性名称写入代码中。访问器方法的名称成为使用它的代码的静态部分。例如: [myAccount setCurrentBalance:@(100.0)];
这样缺乏灵活性,KVC提供了使用字符串标识符访问对象属性的更通用的机制。
使用 key 和 keyPath 标识对象的属性
key: 标识特定属性的字符串。通常表示属性的 key 是代码中显示的属性本身的名称。 key 必须使用ASCII 编码,可能不包含空格,并且通常是以小写字母开头(URL 除外)。 上面的赋值过程使用 KVC 表示: [myAccount setValue:@(100.0) forKey:@"currentBalance"];
keyPath: 用来指定要遍历的对象属性序列的一串使用 “.” 分隔的 key。序列中的第一个键的属性是相对于接受者的,并且每个后续键是相对于前一个属性的值的。当需要使用一个方法来向下逐级获取对象层次结构时,keyPath 特别有用。 例如,owner.address.street
应用于银行账户实例的keyPath 是指存储在银行账户所有者地址中的 street
字符串的值。
访问集合属性
符合键值编码的对象以与公开其他属性相同的方式公开其多对多属性。您可以使用 valueForKey:
或 setValue:forKey:
来获取或设置集合属性。但是,当你想要操作这些集合内容的时候,使用协议定义的可变代理方法通常是最有效的。 该协议为集合对象访问定义了三种不同的代理方法,每种方法都有一个key和key path变量:
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
返回一个行为类似NSMutableArray
的代理对象mutableSetValueForKey:
和mutableSetValueFOrKeyPath:
返回一个行为类似NSMutableSet
的代理对象mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
返回一个行为类似NSMutableOrderedSet
的代理对象 当您对代理对象进行操作,向对象添加元素,从中删除元素或者替换其中的元素时,协议的默认实现会相应地修改基础属性。这比使用valueForKey:
获取一个不可变的集合对象,再创建一个可修改的集合,然后把修改后的集合通过setValue:forKey:
更有效。在许多情况下,它比直接使用可变属性也是更有效的。这些方法为持有集合对象的对象们提供了维护 KVO 特性的好处。
1 | - (void)accessingCollectionProperties { |
使用集合操作符
当您向 valueForKeyPath:
消息发送符合键值编码的对象时,可以在 keyPath 中嵌入集合运算符。集合运算符是一个小的关键字列表之一,前面是一个 @ 符号,它指定了 getter 应该执行的操作,以便在返回之前以某种方式操作数据。NSObject
为 valueForKeyPath:
提供了默认实现。 当 keyPath 包含集合运算符时,运算符之前的部分称为左键路径,指示相对于消息接受者操作的集合,当你直接向一个集合(例如 NSArray
)发送消息时左键路径或许可以省略。操作符之后的部分称为右键路径,指定操作符应处理的集合中的属性,除了 @count
之外的所有操作符都需要一个右键路径。

集合运算符表现出三种基本类型的行为:
- 聚合运算符以某种方式合并集合的对象,并返回通常与右键路径中指定的属性的数据类型匹配的单个对象。
@count
是一个例外,它没有正确的关键路径并始终将返回一个NSNumber
实例。包括:@avg
/@count
/@max
/@min
/@sum
。 - 数组运算符返回一个
NSArray
实例,该实例包含命名集合中保存的对象的某个子集。包含:@distinctUnionOfObjects
/@unionOfObjects
。 - 嵌套运算符处理包含其他集合的集合,并根据操作符返回一个
NSArray
或NSSet
实例,它以某种方式组合嵌套集合的对象。包含:@distinctUnionOfArrays
/@unionOfArrays
/@distinctUnionOfSets
。
1 | - (void)usingCollectionOperators { |
- 获取数组里的“最大、最小、平均、求和”
1 | NSArray *array = @[@"1",@"3",@2,@9.5,@"1.2"]; |
- 删除重复数据
1 | NSArray *array = @[@"name", @"w", @"aa", @"zxp", @"aa"]; //返回的是一个新的数组 |
- 同样可以嵌套使用,先剔除 name 对应值的重复数据再取值
1 | NSArray *array = @[ @{@"title":@"zxp",@"name":@"zhangxiaoping"}, @{@"title":@"zxp2",@"name":@"zhangxiaoping2"}, @{@"title":@"zxp",@"name":@"zhangxiaoping3"}, @{@"title":@"zxp",@"name":@"zhangxiaoping"}]; |
- 进行实例方法的调用
1 | NSArray *array = @[@"name", @"w", @"aa", @"ZXPing"]; |
相当于数组中的每个成员执行了uppercaseString
方法,然后把返回的对象组成一个新数组返回。既然可以用uppercaseString
方法,那么NSString的其他方法也可以,比如[array valueForKeyPath:@"length"]
。当然,其他对象的实例方法也可以以此类推来进行调用~!
访问者搜索模式
NSObject 提供的 NSkeyValueCoding 协议的默认实现使用明确定义的规则集将基于键的访问器调用映射到对象的基础属性。这些协议方法使用 “key” 在其自己的对象实例中搜索访问器、实例变量以及遵循某个命名规则的相关方法。虽然您很少修改此默认搜索,但了解它的工作方式会有所帮助,既可以跟踪键值编码对象的行为,也可以使您自己的对象兼容 KVC。
Getter 的搜索模式
valueForKey:
的默认实现是,给定 key
参数作为输入,通过下面的过程,在接收valueForKey:
调用的类实例中操作。
- 按顺序搜索访问器方法
get<Key>
/<key>
/is<Key>
/_<key>
。如果找到,调用该方法并且带着方法的调用结果调转到第5步执行;否则,继续下一步。 - 如果没有找到简单的访问方法,搜索其名称匹配某些模式的方法的实例。其中匹配模式包含
countOf<Key>
,objectIn<Key>AtIndex:
(对应于NSArray
定义的基本方法),和<key>AtIndexs:
(对应于NSArray
的方法objectsAtIndexs:
) 一旦找到第一个和其他两个中的至少一个,则创建一个响应所以NSArray
方法并返回该方法的集合代理对象。否则,执行第3步。 代理对象随后将任何NSArray
接收到的一些组合的消息。**实际上,与符合键值编码对象一起工作的代理对象允许底层属性的行为就像它是NSArray
一样,即便它不是。 - 如果没有找到简单的访问器方法或数组访问方法组,则寻找三个方法
countOf<Key>
/enumeratorOf<Key>
/memberOf<Key>:
,对应NSSet
类的基本方法。 如果三个方法全找到了,则创建一个集合代理对象来响应所有的NSSet方法并返回。否则,执行第4步。 - 如果上面的方法都没有找到,并且接受者的类方法
accessInstanceVariablesDirectly
返回YES
(默认 YES),则按序搜索以下实例变量:_<key>
/_is<Key>
/<key>
/is<Key>
。如果找到其中之一,直接获取实例变量的值并跳转到第5步;否则执行第6步。 - 如果检索到的属性值是对象指针,则只返回结果;如果值是受
NSNumber
支持的标量,则将其存储在NSNumber
实例中并返回;如果结果是NSNumber
不支持的标量,则转换成NSValue
对象并返回 - 如果以上所有的尝试都失败了,则调用
valueForUndefinedKey:
,这个方法默认抛出异常,NSObject
的子类可以重写来自定义行为。
Setter 的搜索模式
setValue:forKey:
的默认实现是给定 key
和 value
作为参数输入,尝试把 value
设置给以 key
命名的属性。过程如下:
- 按序搜索
set<Key>:
或_set<Key>
,如果找到,则使用输入参数调用并结束。 - 如果没有找到简单的访问器方法,并且如果类方法
accessInstanceVariablesDirectly
返回YES
(默认为 YES),则按序搜索以下实例变量:_<key>
/_is<Key>
/<key>
/is<Key>
,如果找到了则直接进行赋值并结束。 - 以上方法皆失败则调用
setValue:forUndefinedKey:
,这个方法默认抛出异常,NSObject
的子类可以自定义。
KVO
Key-Value Observing 提供了一种机制,允许对象把自身属性的更改通知给其他属性。它对应用程序中 Model 和Controller 层之间的通信特别有用。通常,控制器对象观察模型对象的属性,视图对象通过控制器观察模型对象的属性。另外,一个模型对象或许会观察另一个模型对象(通常用与确认从属值何时改变)或甚至自身(再次确认从属值何时改变)。 你可以观察属性,包括简单属性,一对一关系和多对多关系。多对多关系的观察者被告知所作出的改变的类型——以及改变中涉及哪些对象。
注册 KVO
- 使用
addObserver:forKeyPath:options:content:
方法来给observer注册一个observed object - 在observer内部实现
observerValueForKeyPath:ofObject:change:context:
来接收更改的通知消息。 - 当不再应该接收消息时,使用
removeObserver:forKeyPath:
方法来反注册观察者。起码也要在observer被移除前调用这个方法。
兼容KVO
为了让特定属性符合 KVO 标准,class 必须满足一下内容:
- 该类必须是符合该属性的 KVC
- 该类会为该属性触发 KVO 通知
- 相关的key已经被成功注册
有两种技术可确保发出 KVO 通知。NSObject 提供自动支持,默认情况下可用于符合键值编码的类的所有属性。通常,如果你遵守 Cocoa 编码和命名约定,则可以使用自动通知,而不必编写任何代码。
手动方式为通知触发时提供了更多的控制权,并且需要额外编码。你可以通过实现 automaticallyNotifiesObserversForKey:
来控制子类属性的自动通知。
注册从属keys
在许多情况下,一个属性的值取决于另一个对象中的一个或多个其他属性的值。如果一个属性的值发生更改,则还应标记派生属性的值以进行更改。
Key-Value Observing 的实现细节
自动 key-value observing 是使用一种叫做 isa-swizzling 的技术实现的。
isa 指针指向维护一个调度表(dispatch table)的对象的类。该调度表包含了指向该类实现的方法的指针,以及其他数据。
当观察者注册对象的属性时,观察对象的 isa 指针被修改,指向中间类而不是真正的类。因此,isa 指针的值不一定反映实例的实际类。
永远不要依赖 isa 指针来确定类成员。而应该使用 class
方法来决定实例所属的类。